/**
	Pulsar engine. Core.
	Log support.

	Copyright (C) 2013-2014 Dmitry Dolgov

	This program is free software; you can redistribute it and/or modify it under the terms of the
	GNU General Public License as published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the GNU General Public License for more details.
*/

#include <pulsar/pulsar.h>

namespace pl
{

/// List of standard log names
const pl_char* LogProcessor::errorLevelNames[_LOG_LEVEL_MAX] =
{
	PL_T("Fatal"),
	PL_T("Error"),
	PL_T("Warn"),
	PL_T("Info"),
	PL_T("Debug")
};

/// Internal string pool
struct _LogString
{
	pl::String<1024> str;
	bool useFlag;
};

/// Internal thread counter
struct _LogEnteraceThread
{
	pl_uintptr threadId; // thread id
	int nestedCounter;   // number of enterance in this thread
};

class LogProcessorImpl
{
public:
	LogProcessorImpl();
	~LogProcessorImpl();

	pl::String<1024>* findStringAndThread(int& threadNestedIndex, pl_uintptr threadId);
	void freeStringAndThread(pl::String<1024>* res, int threadNestedIndex);
	bool isPassed(const pl_char* lib, pl::LogLevel level) const;
	void setLogLevel(LogLevel level, const pl_char* libName);
	inline int getThreadCount(int index) const
	{
		return _threadEnteranceList[index].nestedCounter;
	}

private:
	/// Allocated string source
	_LogString* _logStringPool;
	/// Pointer to internal string pool
	_LogEnteraceThread* _threadEnteranceList;


	/// Allocation critical section
	CriticalSection _allocCs;
	/// Generic level log
	LogLevel _logLevel;
	bool _libFilterLevel[256];
	LogLevel _libFilterValue[256];
};

LogProcessorImpl* LogProcessor::_pImpl = NULL;
int LogProcessor::_msgCount = 0;
static pl_uint32 _maxConcurrentThread = 20;

/// ILogTracer collection
List<ILogTracer> ILogTracer::_handlerList;

} // namespace pl


/**
	Initialize log core
	Internal function. Called automatically.
*/
void pl::LogProcessor::_init()
{
	if (_pImpl != NULL)
	{
		PL_ASSERTE(FALSE, PL_T("Secondary initialization"));
		return;
	}
	_pImpl = PL_NEW LogProcessorImpl;
	return;
}

/**
	Shutdown log core
	Internal function. Called automatically.
*/
void pl::LogProcessor::_term()
{
	PL_ASSERTE(_pImpl != NULL, PL_T("Second shutdown"));
	PL_DELETE _pImpl;
	_pImpl = NULL;
	return;
}


/**
	Set maximum threads available for logging
*/
void pl::StartupFuncBase::setMaxLogThreads(pl_uint32 nMaxThreads)
{
	_maxConcurrentThread = nMaxThreads;
	return;
}

/**
	Dispatch all data to log subscribers
	Internal function - called from plLog*** macro()
*/
void pl::ILogTracer::_dispatch(const pl::LogData &params)
{
	pl::ListEnumerator<ILogTracer> enumForward(_handlerList);
	while (!enumForward.isFinished())
	{
		enumForward->onLogMessage(params);
		if (params.level == LOG_LEVEL_FATAL)
		{
			// Send immediate request to flush data
			enumForward->onLogFlush();
		}
	}
	return;
}

/**
	Dispatch all data to log subscribers (when recusrion detected)
	Internal function - called from plLog*** macro()
*/
void pl::ILogTracer::_dispatch2(const pl::LogData &params)
{
	pl::ListEnumerator<ILogTracer> enumForward(_handlerList);
	while (!enumForward.isFinished())
	{
		enumForward->onLogMessage2(params);
	}
	return;
}

/**
	Current log handler
	Internal ctor. Called automatically from plLog***() macro
*/
pl::LogProcessor::LogProcessor(const pl_char* lib, pl::LogLevel level) :
_isPassed(false),
_lib(lib),
_level(level),
_storage(NULL),
_threadNestedIndex(-1)
{
	if (_pImpl == NULL)
	{
		return;
	}

	pl_uintptr threadId = pl::getThreadId();

	// Looking for string storage and thread nexted index
	_storage = _pImpl->findStringAndThread(_threadNestedIndex, threadId);
	if (_storage == NULL)
	{
		return;
	}

	// Checking isPassed() conditions - library name and log level
	_isPassed = _pImpl->isPassed(_lib, _level);
	return;
}

/**
	Current log handler
	Internal ctor. Called automatically from plLog***() macro
*/
pl::LogProcessor::~LogProcessor()
{
	if (_pImpl == NULL)
	{
		return;
	}
	_pImpl->freeStringAndThread(_storage, _threadNestedIndex);
	return;
}

/**
	\param level min level to pass messages
	\param libName library name (NULL - set default level for all handlers)
	Function sets minimum allowed log level (INFO by default).
*/
void pl::LogProcessor::setLogLevel(LogLevel level, const pl_char* libName /*= NULL*/)
{
	if (_pImpl != NULL)
	{
		_pImpl->setLogLevel(level, libName);
	}
	return;
}

/**
	\param file file name whete plLog***() occured
	\param line file number
	\param func function name
	Pass the current message into subscribers
 */
void pl::LogProcessor::dispatch(const pl_char* file, pl_int32 line, const pl_char* func)
{
	if (!isPassed())
	{
		return;
	}

	LogData msgParams;

	msgParams.number = _msgCount++;
	msgParams.msg = _storage->get();
	msgParams.time = pl::getTimeMS();
	msgParams.fileName = file;
	msgParams.lineNmb = line;
	msgParams.function = func;
	msgParams.level = _level;
	msgParams.lib = _lib;

	switch (_pImpl->getThreadCount(_threadNestedIndex))
	{
		case 1:
			// Standard sending
			ILogTracer::_dispatch(msgParams);
			break;
		case 2:
			// Recusrion sending (level 2)
			ILogTracer::_dispatch2(msgParams);
			break;
		default:
			// Recusrion sending (level 3 or more) - skip
			break;
	}
	return;
}

bool pl::LogProcessor::isPassed() const
{
	if (!_isPassed || _threadNestedIndex == -1)
	{
		return false;
	}
	return _pImpl->getThreadCount(_threadNestedIndex) < 3;
}

pl::LogProcessorImpl::LogProcessorImpl()
{
	pl_uint32 i;

	if (_maxConcurrentThread > 0)
	{
		_logStringPool = PL_NEW _LogString[_maxConcurrentThread];
		for (i=0; i<_maxConcurrentThread; i++)
		{
			_logStringPool[i].useFlag = false;
		}

		_threadEnteranceList = PL_NEW _LogEnteraceThread[_maxConcurrentThread];
		for (i=0; i<_maxConcurrentThread; i++)
		{
			_threadEnteranceList[i].nestedCounter = 0;
			_threadEnteranceList[i].threadId = 0;
		}
	}
	return;
}

pl::LogProcessorImpl::~LogProcessorImpl()
{
	PL_DELETE [] _threadEnteranceList;
	_threadEnteranceList = NULL;

	PL_DELETE [] _logStringPool;
	_logStringPool = NULL;

	return;
}

pl::String<1024>* pl::LogProcessorImpl::findStringAndThread(int& threadNestedIndex, pl_uintptr threadId)
{
	pl_uint32 i;
	pl::Lock l(&_allocCs);
	pl::String<1024>* ret = NULL;

	for (i=0; i<_maxConcurrentThread; i++)
	{
		if (!_logStringPool[i].useFlag)
		{
			_logStringPool[i].useFlag = true;
			ret = &_logStringPool[i].str;
			ret->clear();
			break;
		}
	}

	// Looking for a thread
	for (i=0; i<_maxConcurrentThread; i++)
	{
		if (_threadEnteranceList[i].threadId == threadId)
		{
			_threadEnteranceList[i].nestedCounter++;
			threadNestedIndex = i;
			break;
		}
	}

	if (threadNestedIndex == -1)
	{
		// Thread not found - use 1st availlable index to save thread information
		for (i=0; i<_maxConcurrentThread; i++)
		{
			if (_threadEnteranceList[i].threadId == 0)
			{
				_threadEnteranceList[i].nestedCounter++;
				_threadEnteranceList[i].threadId = threadId;
				threadNestedIndex = i;
				break;
			}
		}
	}

	if (threadNestedIndex == -1)
	{
		// No much logs available.
#if defined(PL_DEBUG)
		PL_BREAK(); // Use pl::StartupFuncBase::setMaxLogThreads() to increase number of threads
#endif
	}
	return ret;
}

void pl::LogProcessorImpl::freeStringAndThread(pl::String<1024>* res, int threadNestedIndex)
{
	pl_uint32 i;
	pl::Lock l(&_allocCs);

	if (res != NULL)
	{
		for (i=0; i<_maxConcurrentThread; i++)
		{
			if (res == &_logStringPool[i].str)
			{
				_logStringPool[i].useFlag = false;
				break;
			}
		}
	}
	if (threadNestedIndex != -1)
	{
		if (--_threadEnteranceList[threadNestedIndex].nestedCounter == 0)
		{
			_threadEnteranceList[threadNestedIndex].threadId = 0;
		}
	}
	return;
}

bool pl::LogProcessorImpl::isPassed(const pl_char* lib, pl::LogLevel level) const
{
	if (lib != NULL)
	{
		// Checking for individual value
		pl_uint8 value = (pl_uint8)stringChecksum(lib);
		if (_libFilterLevel[value])
		{
			return level <= _libFilterValue[value];
		}

		// Try to filter with additinal names: "LIB:SUBNAME"
		const pl_char* delimiter = pl_strchr(lib, PL_T(':'));
		if (delimiter != NULL)
		{
			value = (pl_uint8)stringChecksum(lib, (pl_uint32)(delimiter - lib));
			if (_libFilterLevel[value])
			{
				return level <= _libFilterValue[value];
			}
		}
	}
	return level <= _logLevel;
}

void pl::LogProcessorImpl::setLogLevel(LogLevel level, const pl_char* libName)
{
	int i;

	if (libName == NULL)
	{
		// Set common log level name
		_logLevel = level;

		// Clear all personal filters
		for (i=0; i<256; i++)
		{
			_libFilterLevel[i] = false;
		}
		return;
	}

	// Set level for selected library
	pl_uint8 value = (pl_uint8)stringChecksum(libName);
	_libFilterLevel[value] = true;
	_libFilterValue[value] = level;
	return;
}

//
// End of file 'pulsar_log.cpp'
//
